package services.intermediate

import models.outstock._

import java.sql.Timestamp
import java.time.format.DateTimeFormatter
import java.time.{LocalDate, LocalDateTime}
import javax.inject.{Inject, Singleton}
import play.api.Logging
import play.api.libs.json.{JsArray, JsValue, Json, JsonValidationError}
import services.OutStockPostParameters

import scala.collection.mutable
import scala.collection.mutable.{ListBuffer, Map}
import scala.concurrent.ExecutionContext

@Singleton
class SumOutStockRev @Inject()(
                                osrRep: OutStockRevRepository,
                                ossRep: OutStockSumRepository
                              )(implicit exec: ExecutionContext)
  extends Logging {

  def osrSumAtDays(date: LocalDate = LocalDate.now, stockNumber: Option[String] = None) = {
    val arg_Date = LocalDateTime.parse(date.toString + "T00:00:00")
    var startTime = System.currentTimeMillis()
    val thisSumStatus = (startTime % 1_000_000_000 - 1_000_000_000).toInt
    val osrRes = osrRep.osrSumFindByDate(
      arg_Date = arg_Date,
      thisSumStatus = thisSumStatus,
      arg_FStockNumberdate = stockNumber
    )
    logger.info(s"$thisSumStatus, ${osrRes.length}")
    val countInfo = new CountInfo
    var resultList = new ListBuffer[String]
    // sum
    var sdoCount = 0 // 单据计数
    val osrSumMap = mutable.Map[String, KeyMap]("k" -> KeyMap())
    osrRes.foreach { outStockID =>
      val osrfe_q = Json.parse(outStockID.osrFEntityInfo.getOrElse("{}"))
      val key: String = outStockID.osrFShopNumber + outStockID.osrFStockNumber
      var tmp_FGYBillNo = true
      sdoCount += 1
      for (vars <- osrfe_q("FEntity").as[JsArray].value) {
        val varsFMaterialNumber = FMaterialNumber(
          FAllAmount = asString(vars("FAllAmount")).toDouble,
          FAmount = asString(vars("FAmount")).toDouble,
          FDiscount = asString(vars("FDiscount")).toDouble,
          FFeeAmount = asString(vars("FFeeAmount")).toDouble,
          FQty = asString(vars("FQty")).toDouble.toInt,
          FServiceAmount = asString(vars("FServiceAmount")).toDouble,
          FIsFree = asString(vars("FIsFree")).toBoolean,
        )
        if (osrSumMap.contains(key)) {
          if (tmp_FGYBillNo) {
            osrSumMap(key).KeyCount += 1
            tmp_FGYBillNo = false
          }
          if (osrSumMap(key).SumNo.getOrElse(0) > outStockID.osrid.getOrElse(0)) {
            osrSumMap(key).SumNo = outStockID.osrid
          }
          if (osrSumMap(key).FEntity.contains(vars("FMaterialNumber").as[String])) {
            osrSumMap(key).FEntity(vars("FMaterialNumber").as[String]).append(varsFMaterialNumber)
          } else {
            osrSumMap(key).FEntity(vars("FMaterialNumber").as[String]) = ListBuffer(varsFMaterialNumber)
          }
        } else {
          tmp_FGYBillNo = false
          osrSumMap(key) = KeyMap(
            SumNo = outStockID.osrid,
            FDate = outStockID.osrFDate.toLocalDateTime,
            FShopNumber = outStockID.osrFShopNumber,
            FStockNumber = outStockID.osrFStockNumber,
            FEntity = Map(vars("FMaterialNumber").as[String] -> ListBuffer(varsFMaterialNumber)),
            KeyCount = 1
          )
        }
      }
    }
    osrSumMap.remove("k")
    //        println("osrSumMap = "+ osrSumMap)

    /**
     * sum Map
     */
    osrSumMap.foreach { f =>
      // OutStockSum
      val ossInfo = ossRep.ossInsert(OutStockSum(id = f._2.SumNo,
        ossFDate = Timestamp.valueOf(f._2.FDate),
        ossFGYBillNo = "XSDO" + f._2.SumNo.getOrElse(0).toString,
        ossFShopNumber = f._2.FShopNumber,
        ossFStockNumber = f._2.FStockNumber
      ))
      //println(ossInfo)
      // OutStockSumFEntity
      var ossfeList: mutable.Set[Int] = mutable.Set()
      for ((k, v) <- osrSumMap(key = f._1).FEntity) {
        var v_FAllAmount = 0.0
        var v_FAmount = 0.0
        var v_FDiscount = 0.0
        var v_FFeeAmount = 0.0
        var v_FQty = 0
        var v_FServiceAmount = 0.0
        var v_FIsFree = true
        for (tmp <- v) {
          //#[0.FAllAmount,1.FAmount,2.FDiscount,3.FFeeAmount,4.FQty,5.FServiceAmount]
          v_FAllAmount += tmp.FAllAmount
          v_FAmount += tmp.FAmount
          v_FDiscount += tmp.FDiscount
          v_FFeeAmount += tmp.FFeeAmount
          v_FQty += tmp.FQty
          v_FServiceAmount += tmp.FServiceAmount
          if (!(tmp.FIsFree)) v_FIsFree = false
        }
        if (((v_FAllAmount + v_FDiscount) * 10000).toInt == 0) {
          v_FAllAmount += 0.001
          v_FAmount += 0.001
        }
        val v_FPrice = (v_FAllAmount + v_FDiscount) / v_FQty //
        /**
         * instert OutStockSumFEntity
         */
        val pFE = OutStockSumFEntity(
          ossOutStockID = ossInfo,
          ossFAllAmount = v_FAllAmount.formatted("%.4f").toDouble, //#2.7963,
          ossFAmount = v_FAmount.formatted("%.4f").toDouble, //# 3.9193,
          ossFDiscount = v_FDiscount.formatted("%.4f").toDouble, //#1.123,
          ossFFeeAmount = v_FFeeAmount.formatted("%.4f").toDouble, //# 0,
          ossFIsFree = v_FIsFree, //# false,
          ossFMaterialNumber = k, //# A0416002",
          ossFPrice = v_FPrice.formatted("%.4f").toDouble, //#1.3064,
          ossFQty = v_FQty, //#3,
          ossFServiceAmount = v_FServiceAmount.formatted("%.4f").toDouble //# 0,
        )
        //println("pFE: "+ pFE)
        val ossfe = ossRep.ossFEInsert(pFE)

        ossfeList += ossfe.getOrElse(0)

        /**
         * sum countInfo
         */
        countInfo.FAllAmount += v_FAllAmount
        countInfo.FAllAmount += v_FAllAmount //#2.7963,
        countInfo.FAmount += v_FAmount //# 3.9193,
        countInfo.FDiscount += v_FDiscount //#1.123,
        countInfo.FFeeAmount += v_FFeeAmount //# 0,
        countInfo.FIsFree = v_FIsFree //# false,
        countInfo.FQty += v_FQty //#3,
        countInfo.FServiceAmount += v_FServiceAmount

      }

      /**
       * update sum result
       */
      // #print(osrDict[key]["SumNo"],":",ossfeLst)
      // println("this shop = ", osrSumMap(f._1).SumNo, ":", ossfeList.length, osrSumMap(key = f._1).FEntity.values.size)
      if (ossfeList.size == osrSumMap(key = f._1).FEntity.values.size) {
        val sumInfo =
          s"""{"DateTime":"${LocalDateTime.now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))}",
             |"RunTime": ${(System.currentTimeMillis() - startTime) / 1000.0},
             |"sdoCount": $sdoCount,
             |"KeyCount" : ${osrSumMap(f._1).KeyCount},
             |"date":"${date.toString}"}""".stripMargin
        ossRep.ossUpdateEnd(OutStockSum(id = osrSumMap(f._1).SumNo, ossSumInfo = Some(sumInfo)))
        osrRep.osrSumUpdateEnd(
          OutStockRecive(
            osrSumStatus = thisSumStatus,
            osrFShopNumber = osrSumMap(f._1).FShopNumber,
            osrFStockNumber = osrSumMap(f._1).FStockNumber,
            osrOutStockSumId = osrSumMap(f._1).SumNo
          ))
        //        println(s"insert Ok: $sumInfo")
        startTime = System.currentTimeMillis()
        resultList += sumInfo
      }

    }
    //    println(resultList)
    logger.info(countInfo.toString)
    resultList
  }

  def osrSum(p: OutStockPostParameters) = {
    logger.info(p.toString)
    val result = new ListBuffer[String]
    if (p.p2.nonEmpty) {
      val dateEnd = LocalDate.now().minusDays(1)
      result ++= osrSumAtDays(dateEnd)
    } else {
      try {
        val startDate = LocalDate.parse(p.sdate.substring(0, 10)) //.atZone(ZoneId.systemDefault()).toInstant
        val endDate = LocalDate.parse(p.sdate.substring(13)) //.atZone(ZoneId.systemDefault()).toInstant
        logger.debug(s"$startDate, \" ~ \", $endDate")
        var dateEnd = startDate
        while (dateEnd.compareTo(endDate) <= 0) {
          logger.debug(dateEnd.toString)
          if (p.p1 == "" || p.p1 == "-1") result ++= osrSumAtDays(dateEnd)
          else result ++= osrSumAtDays(dateEnd, Some(p.p1))
          dateEnd = dateEnd.plusDays(1);
        }
        result
      }
      catch {
        case e: Exception => logger.info("osrSum", e); new ListBuffer[String]
      }
    }
  }

  def asString(v: JsValue) = v.asOpt[String].getOrElse(v.toString)

}

case class KeyMap(
                   var SumNo: Option[Int] = None,
                   FDate: LocalDateTime = LocalDateTime.now(),
                   FShopNumber: String = "",
                   FStockNumber: String = "",
                   FEntity: Map[String, ListBuffer[FMaterialNumber]] = Map("k" -> mutable.ListBuffer()),
                   var KeyCount: Int = 0
                 )

case class FMaterialNumber(
                            FAllAmount: Double,
                            FAmount: Double,
                            FDiscount: Double,
                            FFeeAmount: Double,
                            FQty: Int,
                            FServiceAmount: Double,
                            FIsFree: Boolean)

case class CountInfo(
                      var FAllAmount: Double = 0.0,
                      var FAmount: Double = 0.0,
                      var FDiscount: Double = 0.0,
                      var FFeeAmount: Double = 0.0,
                      var FQty: Int = 0,
                      var FServiceAmount: Double = 0.0,
                      var FIsFree: Boolean = true
                    )